import {Component} from '../Component.js';
import {math} from '../math/math.js';

/**
 * @desc Meditates mouse, touch and keyboard events for various interaction controls.
 *
 * Ordinarily, you would only use this component as a utility to help manage input events and state for your
 * own custom input handlers.
 *
 * * Located at {@link Scene#input}
 * * Used by (at least) {@link CameraControl}
 *
 * ## Usage
 *
 * Subscribing to mouse events on the canvas:
 *
 * ````javascript
 * import {Viewer} from "xeokit-sdk.es.js";
 *
 * const viewer = new Viewer({
 *      canvasId: "myCanvas"
 * });
 *
 * const input = viewer.scene.input;
 *
 * const onMouseDown = input.on("mousedown", (canvasCoords) => {
 *       console.log("Mouse down at: x=" + canvasCoords[0] + ", y=" + coords[1]);
 * });
 *
 * const onMouseUp = input.on("mouseup", (canvasCoords) => {
 *       console.log("Mouse up at: x=" + canvasCoords[0] + ", y=" + canvasCoords[1]);
 * });
 *
 * const onMouseClicked = input.on("mouseclicked", (canvasCoords) => {
 *      console.log("Mouse clicked at: x=" + canvasCoords[0] + ", y=" + canvasCoords[1]);
 * });
 *
 * const onDblClick = input.on("dblclick", (canvasCoords) => {
 *       console.log("Double-click at: x=" + canvasCoords[0] + ", y=" + canvasCoords[1]);
 * });
 * ````
 *
 * Subscribing to keyboard events on the canvas:
 *
 * ````javascript
 * const onKeyDown = input.on("keydown", (keyCode) => {
 *      switch (keyCode) {
 *          case this.KEY_A:
 *              console.log("The 'A' key is down");
 *              break;
 *
 *          case this.KEY_B:
 *              console.log("The 'B' key is down");
 *              break;
 *
 *          case this.KEY_C:
 *              console.log("The 'C' key is down");
 *              break;
 *
 *          default:
 *              console.log("Some other key is down");
 *      }
 * });
 *
 * const onKeyUp = input.on("keyup", (keyCode) => {
 *      switch (keyCode) {
 *          case this.KEY_A:
 *              console.log("The 'A' key is up");
 *              break;
 *
 *          case this.KEY_B:
 *              console.log("The 'B' key is up");
 *              break;
 *
 *          case this.KEY_C:
 *              console.log("The 'C' key is up");
 *              break;
 *
 *          default:
 *              console.log("Some other key is up");
 *      }
 *  });
 * ````
 *
 * Checking if keys are down:
 *
 * ````javascript
 * const isCtrlDown = input.ctrlDown;
 * const isAltDown = input.altDown;
 * const shiftDown = input.shiftDown;
 * //...
 *
 * const isAKeyDown = input.keyDown[input.KEY_A];
 * const isBKeyDown = input.keyDown[input.KEY_B];
 * const isShiftKeyDown = input.keyDown[input.KEY_SHIFT];
 * //...
 *
 * ````
 * Unsubscribing from events:
 *
 * ````javascript
 * input.off(onMouseDown);
 * input.off(onMouseUp);
 * //...
 * ````
 *
 * ## Disabling all events
 *
 * Event handling is enabled by default.
 *
 * To disable all events:
 *
 * ````javascript
 * myViewer.scene.input.setEnabled(false);
 * ````
 * To enable all events again:
 *
 * ````javascript
 * myViewer.scene.input.setEnabled(true);
 * ````
 *
 * ## Disabling keyboard input
 *
 * When the mouse is over the canvas, the canvas will consume keyboard events. Therefore, sometimes we need
 * to disable keyboard control, so that other UI elements can get those events.
 *
 * To disable keyboard events:
 *
 * ````javascript
 * myViewer.scene.input.setKeyboardEnabled(false);
 * ````
 *
 * To enable keyboard events again:
 *
 * ````javascript
 * myViewer.scene.input.setKeyboardEnabled(true)
 * ````
 */
class Input extends Component {

    /**
     * @private
     */
    constructor(owner, cfg = {}) {

        super(owner, cfg);

        /**
         * Code for the BACKSPACE key.
         * @property KEY_BACKSPACE
         * @final
         * @type {Number}
         */
        this.KEY_BACKSPACE = 8;

        /**
         * Code for the TAB key.
         * @property KEY_TAB
         * @final
         * @type {Number}
         */
        this.KEY_TAB = 9;

        /**
         * Code for the ENTER key.
         * @property KEY_ENTER
         * @final
         * @type {Number}
         */
        this.KEY_ENTER = 13;

        /**
         * Code for the SHIFT key.
         * @property KEY_SHIFT
         * @final
         * @type {Number}
         */
        this.KEY_SHIFT = 16;

        /**
         * Code for the CTRL key.
         * @property KEY_CTRL
         * @final
         * @type {Number}
         */
        this.KEY_CTRL = 17;

        /**
         * Code for the ALT key.
         * @property KEY_ALT
         * @final
         * @type {Number}
         */
        this.KEY_ALT = 18;

        /**
         * Code for the PAUSE_BREAK key.
         * @property KEY_PAUSE_BREAK
         * @final
         * @type {Number}
         */
        this.KEY_PAUSE_BREAK = 19;

        /**
         * Code for the CAPS_LOCK key.
         * @property KEY_CAPS_LOCK
         * @final
         * @type {Number}
         */
        this.KEY_CAPS_LOCK = 20;

        /**
         * Code for the ESCAPE key.
         * @property KEY_ESCAPE
         * @final
         * @type {Number}
         */
        this.KEY_ESCAPE = 27;

        /**
         * Code for the PAGE_UP key.
         * @property KEY_PAGE_UP
         * @final
         * @type {Number}
         */
        this.KEY_PAGE_UP = 33;

        /**
         * Code for the PAGE_DOWN key.
         * @property KEY_PAGE_DOWN
         * @final
         * @type {Number}
         */
        this.KEY_PAGE_DOWN = 34;

        /**
         * Code for the END key.
         * @property KEY_END
         * @final
         * @type {Number}
         */
        this.KEY_END = 35;

        /**
         * Code for the HOME key.
         * @property KEY_HOME
         * @final
         * @type {Number}
         */
        this.KEY_HOME = 36;

        /**
         * Code for the LEFT_ARROW key.
         * @property KEY_LEFT_ARROW
         * @final
         * @type {Number}
         */
        this.KEY_LEFT_ARROW = 37;

        /**
         * Code for the UP_ARROW key.
         * @property KEY_UP_ARROW
         * @final
         * @type {Number}
         */
        this.KEY_UP_ARROW = 38;

        /**
         * Code for the RIGHT_ARROW key.
         * @property KEY_RIGHT_ARROW
         * @final
         * @type {Number}
         */
        this.KEY_RIGHT_ARROW = 39;

        /**
         * Code for the DOWN_ARROW key.
         * @property KEY_DOWN_ARROW
         * @final
         * @type {Number}
         */
        this.KEY_DOWN_ARROW = 40;

        /**
         * Code for the INSERT key.
         * @property KEY_INSERT
         * @final
         * @type {Number}
         */
        this.KEY_INSERT = 45;

        /**
         * Code for the DELETE key.
         * @property KEY_DELETE
         * @final
         * @type {Number}
         */
        this.KEY_DELETE = 46;

        /**
         * Code for the 0 key.
         * @property KEY_NUM_0
         * @final
         * @type {Number}
         */
        this.KEY_NUM_0 = 48;

        /**
         * Code for the 1 key.
         * @property KEY_NUM_1
         * @final
         * @type {Number}
         */
        this.KEY_NUM_1 = 49;

        /**
         * Code for the 2 key.
         * @property KEY_NUM_2
         * @final
         * @type {Number}
         */
        this.KEY_NUM_2 = 50;

        /**
         * Code for the 3 key.
         * @property KEY_NUM_3
         * @final
         * @type {Number}
         */
        this.KEY_NUM_3 = 51;

        /**
         * Code for the 4 key.
         * @property KEY_NUM_4
         * @final
         * @type {Number}
         */
        this.KEY_NUM_4 = 52;

        /**
         * Code for the 5 key.
         * @property KEY_NUM_5
         * @final
         * @type {Number}
         */
        this.KEY_NUM_5 = 53;

        /**
         * Code for the 6 key.
         * @property KEY_NUM_6
         * @final
         * @type {Number}
         */
        this.KEY_NUM_6 = 54;

        /**
         * Code for the 7 key.
         * @property KEY_NUM_7
         * @final
         * @type {Number}
         */
        this.KEY_NUM_7 = 55;

        /**
         * Code for the 8 key.
         * @property KEY_NUM_8
         * @final
         * @type {Number}
         */
        this.KEY_NUM_8 = 56;

        /**
         * Code for the 9 key.
         * @property KEY_NUM_9
         * @final
         * @type {Number}
         */
        this.KEY_NUM_9 = 57;

        /**
         * Code for the A key.
         * @property KEY_A
         * @final
         * @type {Number}
         */
        this.KEY_A = 65;

        /**
         * Code for the B key.
         * @property KEY_B
         * @final
         * @type {Number}
         */
        this.KEY_B = 66;

        /**
         * Code for the C key.
         * @property KEY_C
         * @final
         * @type {Number}
         */
        this.KEY_C = 67;

        /**
         * Code for the D key.
         * @property KEY_D
         * @final
         * @type {Number}
         */
        this.KEY_D = 68;

        /**
         * Code for the E key.
         * @property KEY_E
         * @final
         * @type {Number}
         */
        this.KEY_E = 69;

        /**
         * Code for the F key.
         * @property KEY_F
         * @final
         * @type {Number}
         */
        this.KEY_F = 70;

        /**
         * Code for the G key.
         * @property KEY_G
         * @final
         * @type {Number}
         */
        this.KEY_G = 71;

        /**
         * Code for the H key.
         * @property KEY_H
         * @final
         * @type {Number}
         */
        this.KEY_H = 72;

        /**
         * Code for the I key.
         * @property KEY_I
         * @final
         * @type {Number}
         */
        this.KEY_I = 73;

        /**
         * Code for the J key.
         * @property KEY_J
         * @final
         * @type {Number}
         */
        this.KEY_J = 74;

        /**
         * Code for the K key.
         * @property KEY_K
         * @final
         * @type {Number}
         */
        this.KEY_K = 75;

        /**
         * Code for the L key.
         * @property KEY_L
         * @final
         * @type {Number}
         */
        this.KEY_L = 76;

        /**
         * Code for the M key.
         * @property KEY_M
         * @final
         * @type {Number}
         */
        this.KEY_M = 77;

        /**
         * Code for the N key.
         * @property KEY_N
         * @final
         * @type {Number}
         */
        this.KEY_N = 78;

        /**
         * Code for the O key.
         * @property KEY_O
         * @final
         * @type {Number}
         */
        this.KEY_O = 79;

        /**
         * Code for the P key.
         * @property KEY_P
         * @final
         * @type {Number}
         */
        this.KEY_P = 80;

        /**
         * Code for the Q key.
         * @property KEY_Q
         * @final
         * @type {Number}
         */
        this.KEY_Q = 81;

        /**
         * Code for the R key.
         * @property KEY_R
         * @final
         * @type {Number}
         */
        this.KEY_R = 82;

        /**
         * Code for the S key.
         * @property KEY_S
         * @final
         * @type {Number}
         */
        this.KEY_S = 83;

        /**
         * Code for the T key.
         * @property KEY_T
         * @final
         * @type {Number}
         */
        this.KEY_T = 84;

        /**
         * Code for the U key.
         * @property KEY_U
         * @final
         * @type {Number}
         */
        this.KEY_U = 85;

        /**
         * Code for the V key.
         * @property KEY_V
         * @final
         * @type {Number}
         */
        this.KEY_V = 86;

        /**
         * Code for the W key.
         * @property KEY_W
         * @final
         * @type {Number}
         */
        this.KEY_W = 87;

        /**
         * Code for the X key.
         * @property KEY_X
         * @final
         * @type {Number}
         */
        this.KEY_X = 88;

        /**
         * Code for the Y key.
         * @property KEY_Y
         * @final
         * @type {Number}
         */
        this.KEY_Y = 89;

        /**
         * Code for the Z key.
         * @property KEY_Z
         * @final
         * @type {Number}
         */
        this.KEY_Z = 90;

        /**
         * Code for the LEFT_WINDOW key.
         * @property KEY_LEFT_WINDOW
         * @final
         * @type {Number}
         */
        this.KEY_LEFT_WINDOW = 91;

        /**
         * Code for the RIGHT_WINDOW key.
         * @property KEY_RIGHT_WINDOW
         * @final
         * @type {Number}
         */
        this.KEY_RIGHT_WINDOW = 92;

        /**
         * Code for the SELECT key.
         * @property KEY_SELECT
         * @final
         * @type {Number}
         */
        this.KEY_SELECT_KEY = 93;

        /**
         * Code for the number pad 0 key.
         * @property KEY_NUMPAD_0
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_0 = 96;

        /**
         * Code for the number pad 1 key.
         * @property KEY_NUMPAD_1
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_1 = 97;

        /**
         * Code for the number pad 2 key.
         * @property KEY_NUMPAD 2
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_2 = 98;

        /**
         * Code for the number pad 3 key.
         * @property KEY_NUMPAD_3
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_3 = 99;

        /**
         * Code for the number pad 4 key.
         * @property KEY_NUMPAD_4
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_4 = 100;

        /**
         * Code for the number pad 5 key.
         * @property KEY_NUMPAD_5
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_5 = 101;

        /**
         * Code for the number pad 6 key.
         * @property KEY_NUMPAD_6
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_6 = 102;

        /**
         * Code for the number pad 7 key.
         * @property KEY_NUMPAD_7
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_7 = 103;

        /**
         * Code for the number pad 8 key.
         * @property KEY_NUMPAD_8
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_8 = 104;

        /**
         * Code for the number pad 9 key.
         * @property KEY_NUMPAD_9
         * @final
         * @type {Number}
         */
        this.KEY_NUMPAD_9 = 105;

        /**
         * Code for the MULTIPLY key.
         * @property KEY_MULTIPLY
         * @final
         * @type {Number}
         */
        this.KEY_MULTIPLY = 106;

        /**
         * Code for the ADD key.
         * @property KEY_ADD
         * @final
         * @type {Number}
         */
        this.KEY_ADD = 107;

        /**
         * Code for the SUBTRACT key.
         * @property KEY_SUBTRACT
         * @final
         * @type {Number}
         */
        this.KEY_SUBTRACT = 109;

        /**
         * Code for the DECIMAL POINT key.
         * @property KEY_DECIMAL_POINT
         * @final
         * @type {Number}
         */
        this.KEY_DECIMAL_POINT = 110;

        /**
         * Code for the DIVIDE key.
         * @property KEY_DIVIDE
         * @final
         * @type {Number}
         */
        this.KEY_DIVIDE = 111;

        /**
         * Code for the F1 key.
         * @property KEY_F1
         * @final
         * @type {Number}
         */
        this.KEY_F1 = 112;

        /**
         * Code for the F2 key.
         * @property KEY_F2
         * @final
         * @type {Number}
         */
        this.KEY_F2 = 113;

        /**
         * Code for the F3 key.
         * @property KEY_F3
         * @final
         * @type {Number}
         */
        this.KEY_F3 = 114;

        /**
         * Code for the F4 key.
         * @property KEY_F4
         * @final
         * @type {Number}
         */
        this.KEY_F4 = 115;

        /**
         * Code for the F5 key.
         * @property KEY_F5
         * @final
         * @type {Number}
         */
        this.KEY_F5 = 116;

        /**
         * Code for the F6 key.
         * @property KEY_F6
         * @final
         * @type {Number}
         */
        this.KEY_F6 = 117;

        /**
         * Code for the F7 key.
         * @property KEY_F7
         * @final
         * @type {Number}
         */
        this.KEY_F7 = 118;

        /**
         * Code for the F8 key.
         * @property KEY_F8
         * @final
         * @type {Number}
         */
        this.KEY_F8 = 119;

        /**
         * Code for the F9 key.
         * @property KEY_F9
         * @final
         * @type {Number}
         */
        this.KEY_F9 = 120;

        /**
         * Code for the F10 key.
         * @property KEY_F10
         * @final
         * @type {Number}
         */
        this.KEY_F10 = 121;

        /**
         * Code for the F11 key.
         * @property KEY_F11
         * @final
         * @type {Number}
         */
        this.KEY_F11 = 122;

        /**
         * Code for the F12 key.
         * @property KEY_F12
         * @final
         * @type {Number}
         */
        this.KEY_F12 = 123;

        /**
         * Code for the NUM_LOCK key.
         * @property KEY_NUM_LOCK
         * @final
         * @type {Number}
         */
        this.KEY_NUM_LOCK = 144;

        /**
         * Code for the SCROLL_LOCK key.
         * @property KEY_SCROLL_LOCK
         * @final
         * @type {Number}
         */
        this.KEY_SCROLL_LOCK = 145;

        /**
         * Code for the SEMI_COLON key.
         * @property KEY_SEMI_COLON
         * @final
         * @type {Number}
         */
        this.KEY_SEMI_COLON = 186;

        /**
         * Code for the EQUAL_SIGN key.
         * @property KEY_EQUAL_SIGN
         * @final
         * @type {Number}
         */
        this.KEY_EQUAL_SIGN = 187;

        /**
         * Code for the COMMA key.
         * @property KEY_COMMA
         * @final
         * @type {Number}
         */
        this.KEY_COMMA = 188;

        /**
         * Code for the DASH key.
         * @property KEY_DASH
         * @final
         * @type {Number}
         */
        this.KEY_DASH = 189;

        /**
         * Code for the PERIOD key.
         * @property KEY_PERIOD
         * @final
         * @type {Number}
         */
        this.KEY_PERIOD = 190;

        /**
         * Code for the FORWARD_SLASH key.
         * @property KEY_FORWARD_SLASH
         * @final
         * @type {Number}
         */
        this.KEY_FORWARD_SLASH = 191;

        /**
         * Code for the GRAVE_ACCENT key.
         * @property KEY_GRAVE_ACCENT
         * @final
         * @type {Number}
         */
        this.KEY_GRAVE_ACCENT = 192;

        /**
         * Code for the OPEN_BRACKET key.
         * @property KEY_OPEN_BRACKET
         * @final
         * @type {Number}
         */
        this.KEY_OPEN_BRACKET = 219;

        /**
         * Code for the BACK_SLASH key.
         * @property KEY_BACK_SLASH
         * @final
         * @type {Number}
         */
        this.KEY_BACK_SLASH = 220;

        /**
         * Code for the CLOSE_BRACKET key.
         * @property KEY_CLOSE_BRACKET
         * @final
         * @type {Number}
         */
        this.KEY_CLOSE_BRACKET = 221;

        /**
         * Code for the SINGLE_QUOTE key.
         * @property KEY_SINGLE_QUOTE
         * @final
         * @type {Number}
         */
        this.KEY_SINGLE_QUOTE = 222;

        /**
         * Code for the SPACE key.
         * @property KEY_SPACE
         * @final
         * @type {Number}
         */
        this.KEY_SPACE = 32;

        /**
         * Code for the left mouse button.
         * @property MOUSE_LEFT_BUTTON
         * @final
         * @type {Number}
         */
        this.MOUSE_LEFT_BUTTON = 260;

        /**
         * Code for the middle mouse button.
         * @property MOUSE_MIDDLE_BUTTON
         * @final
         * @type {Number}
         */
        this.MOUSE_MIDDLE_BUTTON = 261;

        /**
         * Code for the right mouse button.
         * @property MOUSE_RIGHT_BUTTON
         * @final
         * @type {Number}
         */
        this.MOUSE_RIGHT_BUTTON = 262;

        /**
         * The canvas element that mouse and keyboards are bound to.
         *
         * @final
         * @type {HTMLCanvasElement}
         */
        this.element = cfg.element;

        /** True whenever ALT key is down.
         *
         * @type {boolean}
         */
        this.altDown = false;

        /** True whenever CTRL key is down.
         *
         * @type {boolean}
         */
        this.ctrlDown = false;

        /** True whenever left mouse button is down.
         *
         * @type {boolean}
         */
        this.mouseDownLeft = false;

        /**
         * True whenever middle mouse button is down.
         *
         * @type {boolean}
         */
        this.mouseDownMiddle = false;

        /**
         * True whenever the right mouse button is down.
         *
         * @type {boolean}
         */
        this.mouseDownRight = false;

        /**
         * Flag for each key that's down.
         *
         * @type {boolean[]}
         */
        this.keyDown = [];

        /** True while input enabled
         *
         * @type {boolean}
         */
        this.enabled = true;

        /** True while keyboard input is enabled.
         *
         * Default value is ````true````.
         *
         * {@link CameraControl} will not respond to keyboard events while this is ````false````.
         *
         * @type {boolean}
         */
        this.keyboardEnabled = true;

        /** True while the mouse is over the canvas.
         *
         * @type {boolean}
         */
        this.mouseover = false;

        /**
         * Current mouse position within the canvas.
         * @type {Number[]}
         */
        this.mouseCanvasPos = math.vec2();

        this._keyboardEventsElement = cfg.keyboardEventsElement || document;

        this._bindEvents();
    }

    _bindEvents() {

        if (this._eventsBound) {
            return;
        }

        this._keyboardEventsElement.addEventListener("keydown", this._keyDownListener = (e) => {
            if (!this.enabled || (!this.keyboardEnabled)) {
                return;
            }
            if (e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
                if (e.keyCode === this.KEY_CTRL) {
                    this.ctrlDown = true;
                } else if (e.keyCode === this.KEY_ALT) {
                    this.altDown = true;
                } else if (e.keyCode === this.KEY_SHIFT) {
                    this.shiftDown = true;
                }
                this.keyDown[e.keyCode] = true;
                this.fire("keydown", e.keyCode, true);
            }
        }, false);

        this._keyboardEventsElement.addEventListener("keyup", this._keyUpListener = (e) => {
            if (!this.enabled || (!this.keyboardEnabled)) {
                return;
            }
            if (e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
                if (e.keyCode === this.KEY_CTRL) {
                    this.ctrlDown = false;
                } else if (e.keyCode === this.KEY_ALT) {
                    this.altDown = false;
                } else if (e.keyCode === this.KEY_SHIFT) {
                    this.shiftDown = false;
                }
                this.keyDown[e.keyCode] = false;
                this.fire("keyup", e.keyCode, true);
            }
        });

        this.element.addEventListener("mouseenter", this._mouseEnterListener = (e) => {
            if (!this.enabled) {
                return;
            }
            this.mouseover = true;
            this._getMouseCanvasPos(e);
            this.fire("mouseenter", this.mouseCanvasPos, true);
        });

        this.element.addEventListener("mouseleave", this._mouseLeaveListener = (e) => {
            if (!this.enabled) {
                return;
            }
            this.mouseover = false;
            this._getMouseCanvasPos(e);
            this.fire("mouseleave", this.mouseCanvasPos, true);
        });

        this.element.addEventListener("mousedown", this._mouseDownListener = (e) => {
            if (!this.enabled) {
                return;
            }
            switch (e.which) {
                case 1:// Left button
                    this.mouseDownLeft = true;
                    break;
                case 2:// Middle/both buttons
                    this.mouseDownMiddle = true;
                    break;
                case 3:// Right button
                    this.mouseDownRight = true;
                    break;
                default:
                    break;
            }
            this._getMouseCanvasPos(e);
            this.element.focus();
            this.fire("mousedown", this.mouseCanvasPos, true);
            if (this.mouseover) {
                e.preventDefault();
            }
        });

        document.addEventListener("mouseup", this._mouseUpListener = (e) => {
            if (!this.enabled) {
                return;
            }
            switch (e.which) {
                case 1:// Left button
                    this.mouseDownLeft = false;
                    break;
                case 2:// Middle/both buttons
                    this.mouseDownMiddle = false;
                    break;
                case 3:// Right button
                    this.mouseDownRight = false;
                    break;
                default:
                    break;
            }
            this.fire("mouseup", this.mouseCanvasPos, true);
            // if (this.mouseover) {
            //     e.preventDefault();
            // }
        }, true);

        document.addEventListener("click", this._clickListener = (e) => {
            if (!this.enabled) {
                return;
            }
            switch (e.which) {
                case 1:// Left button
                    this.mouseDownLeft = false;
                    this.mouseDownRight = false;
                    break;
                case 2:// Middle/both buttons
                    this.mouseDownMiddle = false;
                    break;
                case 3:// Right button
                    this.mouseDownLeft = false;
                    this.mouseDownRight = false;
                    break;
                default:
                    break;
            }
            this._getMouseCanvasPos(e);
            this.fire("click", this.mouseCanvasPos, true);
            if (this.mouseover) {
                e.preventDefault();
            }
        });

        document.addEventListener("dblclick", this._dblClickListener = (e) => {
            if (!this.enabled) {
                return;
            }
            switch (e.which) {
                case 1:// Left button
                    this.mouseDownLeft = false;
                    this.mouseDownRight = false;
                    break;
                case 2:// Middle/both buttons
                    this.mouseDownMiddle = false;
                    break;
                case 3:// Right button
                    this.mouseDownLeft = false;
                    this.mouseDownRight = false;
                    break;
                default:
                    break;
            }
            this._getMouseCanvasPos(e);
            this.fire("dblclick", this.mouseCanvasPos, true);
            if (this.mouseover) {
                e.preventDefault();
            }
        });

        const tickifedMouseMoveFn = this.scene.tickify(
            () => this.fire("mousemove", this.mouseCanvasPos, true)
        );

        this.element.addEventListener("mousemove", this._mouseMoveListener = (e) => {
            if (!this.enabled) {
                return;
            }
            this._getMouseCanvasPos(e);
            tickifedMouseMoveFn();  
            if (this.mouseover) {
                e.preventDefault();
            }
        });

        this.element.addEventListener("contextmenu", this._contextmenuListener = (e) => {
            if (!this.enabled) {
                return;
            }
            this._getMouseCanvasPos(e);
            this.fire("contextmenu", this.mouseCanvasPos, true);
        });

        const tickifiedMouseWheelFn = this.scene.tickify(
            (delta) => { this.fire("mousewheel", delta, true); }
        );

        this.element.addEventListener("wheel", this._mouseWheelListener = (e, d) => {
            if (!this.enabled) {
                return;
            }
            const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
            tickifiedMouseWheelFn(delta);
        }, {passive: true});

        // mouseclicked

        {
            let downX;
            let downY;
            // Tolerance between down and up positions for a mouse click
            const tolerance = 2;
            this.on("mousedown", (params) => {
                downX = params[0];
                downY = params[1];
            });
            this.on("mouseup", (params) => {
                if (downX >= (params[0] - tolerance) &&
                    downX <= (params[0] + tolerance) &&
                    downY >= (params[1] - tolerance) &&
                    downY <= (params[1] + tolerance)) {
                    this.fire("mouseclicked", params, true);
                }
            });
        }

        this.element.addEventListener("touchstart", this._touchstartListener = (e) => {
            if (!this.enabled) {
                return;
            }
            [...e.changedTouches].forEach(e => {
                this.fire("touchstart", [ e.identifier, this._getTouchCanvasPos(e) ], true);
            });
        });

        this.element.addEventListener("touchend", this._touchendListener = (e) => {
            if (!this.enabled) {
                return;
            }
            [...e.changedTouches].forEach(e => {
                this.fire("touchend", [ e.identifier, this._getTouchCanvasPos(e) ], true);
            });
        });

        this._eventsBound = true;
    }

    _unbindEvents() {
        if (!this._eventsBound) {
            return;
        }
        this._keyboardEventsElement.removeEventListener("keydown", this._keyDownListener);
        this._keyboardEventsElement.removeEventListener("keyup", this._keyUpListener);
        this.element.removeEventListener("mouseenter", this._mouseEnterListener);
        this.element.removeEventListener("mouseleave", this._mouseLeaveListener);
        this.element.removeEventListener("mousedown", this._mouseDownListener);
        document.removeEventListener("mouseup", this._mouseDownListener);
        document.removeEventListener("click", this._clickListener);
        document.removeEventListener("dblclick", this._dblClickListener);
        this.element.removeEventListener("mousemove", this._mouseMoveListener);
        this.element.removeEventListener("contextmenu", this._contextmenuListener);
        this.element.removeEventListener("wheel", this._mouseWheelListener);
        this.element.removeEventListener("touchstart", this._touchstartListener);
        this.element.removeEventListener("touchend", this._touchendListener);
        if (window.OrientationChangeEvent) {
            window.removeEventListener('orientationchange', this._orientationchangedListener);
        }
        if (window.DeviceMotionEvent) {
            window.removeEventListener('devicemotion', this._deviceMotionListener);
        }
        if (window.DeviceOrientationEvent) {
            window.removeEventListener("deviceorientation", this._deviceOrientListener);
        }
        this._eventsBound = false;
    }

    _getTouchCanvasPos(event) {
        let element = event.target;
        let totalOffsetLeft = 0;
        let totalOffsetTop = 0;
        while (element.offsetParent) {
            totalOffsetLeft += element.offsetLeft;
            totalOffsetTop += element.offsetTop;
            element = element.offsetParent;
        }
        return [ event.pageX - totalOffsetLeft, event.pageY - totalOffsetTop ];
    }

    _getMouseCanvasPos(event) {
        if (!event) {
            event = window.event;
            this.mouseCanvasPos[0] = event.x;
            this.mouseCanvasPos[1] = event.y;
        } else {
            let element = event.target;

            const rect = element.getBoundingClientRect();

            this.mouseCanvasPos[0] = event.clientX - rect.left;
            this.mouseCanvasPos[1] = event.clientY - rect.top;
        }
    }

    /**
     * Sets whether input handlers are enabled.
     *
     * Default value is ````true````.
     *
     * @param {Boolean} enable Indicates if input handlers are enabled.
     */
    setEnabled(enable) {
        if (this.enabled !== enable) {
            this.fire("enabled", this.enabled = enable);
        }
    }

    /**
     * Gets whether input handlers are enabled.
     *
     * Default value is ````true````.
     *
     * @returns {Boolean} Indicates if input handlers are enabled.
     */
    getEnabled() {
        return this.enabled;
    }

    /**
     * Sets whether or not keyboard input is enabled.
     *
     * Default value is ````true````.
     *
     * {@link CameraControl} will not respond to keyboard events while this is set ````false````.
     *
     * @param {Boolean} value Indicates whether keyboard input is enabled.
     */
    setKeyboardEnabled(value) {
        this.keyboardEnabled = value;
    }

    /**
     * Gets whether keyboard input is enabled.
     *
     * Default value is ````true````.
     *
     * {@link CameraControl} will not respond to keyboard events while this is set ````false````.
     *
     * @returns {Boolean} Returns whether keyboard input is enabled.
     */
    getKeyboardEnabled() {
        return this.keyboardEnabled;
    }

    /**
     * @private
     */
    destroy() {
        super.destroy();
        this._unbindEvents();
    }
}

export {Input};
